package no.nordicsemi.support.v18.scanner;
import ohos.utils.PlainArray;
import ohos.utils.SequenceUuid;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.Objects;

public final class ScanRecord {
	private static final String TAG = "ScanRecord";
	private static final int DATA_TYPE_FLAGS = 0x01;
	private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
	private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
	private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
	private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
	private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
	private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
	private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
	private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
	private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
	private static final int DATA_TYPE_SERVICE_DATA_16_BIT = 0x16;
	private static final int DATA_TYPE_SERVICE_DATA_32_BIT = 0x20;
	private static final int DATA_TYPE_SERVICE_DATA_128_BIT = 0x21;
	private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
	private static final String LABEL_LOG ="ScanRecord" ;
	private final int advertiseFlags;
	@Nullable
	private final List<SequenceUuid> serviceUuids;
	@Nullable private final PlainArray<byte[]> manufacturerSpecificData;
	@Nullable private final Map<SequenceUuid, byte[]> serviceData;
	private final int txPowerLevel;
	private final String deviceName;
	private final String bytes;
	public int getAdvertiseFlags() {
		return advertiseFlags;
	}
	@Nullable
	public List<SequenceUuid> getServiceUuids() {
		return serviceUuids;
	}
	@Nullable
	public PlainArray<byte[]> getManufacturerSpecificData() {
		return manufacturerSpecificData;
	}

	@Nullable
	public Map<SequenceUuid, byte[]> getServiceData() {
		return serviceData;
	}
	@Nullable
	public byte[] getServiceData( final Object serviceDataUuid) {
		if (serviceDataUuid == null || serviceData == null) {
			return null;
		}
		return serviceData.get(serviceDataUuid);
	}

	public int getTxPowerLevel() {
		return txPowerLevel;
	}

	public String getDeviceName() {
		return deviceName;
	}

	@Nullable
	public byte[] getBytes() {
		return bytes.getBytes();
	}

	private ScanRecord(@Nullable final List<SequenceUuid> serviceUuids,
					   @Nullable final PlainArray<byte[]> manufacturerData,
					   @Nullable final Map<SequenceUuid, byte[]> serviceData,
					   final int advertiseFlags, final int txPowerLevel,
					   final String localName, final byte[] bytes) {
		this.serviceUuids = serviceUuids;
		this.manufacturerSpecificData = manufacturerData;
		this.serviceData = serviceData;
		this.deviceName = localName;
		this.advertiseFlags = advertiseFlags;
		this.txPowerLevel = txPowerLevel;
		this.bytes = Arrays.toString(bytes);
	}

	@Nullable
	/* package */ static ScanRecord parseFromBytes(@Nullable final byte[] scanRecord) {
		if (scanRecord == null) {
			return null;
		}

		int currentPos = 0;
		int advertiseFlag = -1;
		int txPowerLevel = Integer.MIN_VALUE;
		String localName = null;
		List<SequenceUuid> serviceUuids = null;
		PlainArray<byte[]> manufacturerData = null;
		Map<SequenceUuid, byte[]> serviceData = null;

		while (currentPos < scanRecord.length) {
			// length is unsigned int.
			final int length = scanRecord[currentPos++] & 0xFF;
			if (length == 0) {
				break;
			}
			final int dataLength = length - 1;
			final int fieldType = scanRecord[currentPos++] & 0xFF;
			switch (fieldType) {
				case DATA_TYPE_FLAGS:
					advertiseFlag = scanRecord[currentPos] & 0xFF;
					break;
				case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
				case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
					if (serviceUuids == null)
						serviceUuids = new ArrayList<>();
					parseServiceUuid(scanRecord, currentPos,
							dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
					break;
				case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
				case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
					if (serviceUuids == null)
						serviceUuids = new ArrayList<>();
					parseServiceUuid(scanRecord, currentPos, dataLength,
							BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
					break;
				case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
				case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
					if (serviceUuids == null)
						serviceUuids = new ArrayList<>();
					parseServiceUuid(scanRecord, currentPos, dataLength,
							BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
					break;
				case DATA_TYPE_LOCAL_NAME_SHORT:
				case DATA_TYPE_LOCAL_NAME_COMPLETE:
					localName = new String(
							extractBytes(scanRecord, currentPos, dataLength));
					break;
				case DATA_TYPE_TX_POWER_LEVEL:
					txPowerLevel = scanRecord[currentPos];
					break;
				case DATA_TYPE_SERVICE_DATA_16_BIT:
				case DATA_TYPE_SERVICE_DATA_32_BIT:
				case DATA_TYPE_SERVICE_DATA_128_BIT:
					int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
					if (fieldType == DATA_TYPE_SERVICE_DATA_32_BIT) {
						serviceUuidLength = BluetoothUuid.UUID_BYTES_32_BIT;
					} else if (fieldType == DATA_TYPE_SERVICE_DATA_128_BIT) {
						serviceUuidLength = BluetoothUuid.UUID_BYTES_128_BIT;
					}

					final byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
							serviceUuidLength);
					final SequenceUuid serviceDataUuid = BluetoothUuid.parseUuidFrom(
							serviceDataUuidBytes);
					final byte[] serviceDataArray = extractBytes(scanRecord,
							currentPos + serviceUuidLength, dataLength - serviceUuidLength);
					if (serviceData == null)
						serviceData = new HashMap<>();
					serviceData.put(serviceDataUuid, serviceDataArray);
					break;
				case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
					final int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) +
							(scanRecord[currentPos] & 0xFF);
					final byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
							dataLength - 2);
					if (manufacturerData == null)
						manufacturerData = new PlainArray<>();
					manufacturerData.put(manufacturerId, manufacturerDataBytes);
					break;
				default:
					break;
			}
			currentPos += dataLength;
		}
		return new ScanRecord(serviceUuids, manufacturerData, serviceData,
				advertiseFlag, txPowerLevel, localName, scanRecord);
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null || getClass() != obj.getClass()) {
			return false;
		}
		ScanRecord other = (ScanRecord) obj;
		return Arrays.equals(bytes.getBytes(), other.bytes.getBytes());
	}

	@Override
	public int hashCode() {
		return Objects.hash(advertiseFlags, serviceUuids, manufacturerSpecificData, serviceData, txPowerLevel, deviceName, bytes);
	}

	@Override
	public String toString() {
		return "ScanRecord [advertiseFlags=" + advertiseFlags + ", serviceUuids=" + serviceUuids
				+ ", manufacturerSpecificData=" + BluetoothLeUtils.toString(manufacturerSpecificData)
				+ ", serviceData=" + BluetoothLeUtils.toString(serviceData)
				+ ", txPowerLevel=" + txPowerLevel + ", deviceName=" + deviceName + "]";
	}

	private static int parseServiceUuid( final byte[] scanRecord,int currentPos, int dataLength
			,final int uuidLength
			,final List<SequenceUuid> serviceUuids) {
		while (dataLength > 0) {
			final byte[] uuidBytes = extractBytes(scanRecord, currentPos,
					uuidLength);
			serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
			dataLength -= uuidLength;
			currentPos += uuidLength;
		}
		return currentPos;
	}

	private static byte[] extractBytes( final byte[] scanRecord,
									   final int start, final int length) {
		byte[] bytes = new byte[length];
		System.arraycopy(scanRecord, start, bytes, 0, length);
		return bytes;
	}
}
